﻿
using System;
using System.Collections;
using System.Text;

namespace HART
{
    /// <summary>
    /// 包含将通过HART协议传输的数据转换为字节数组及其反向转换的方法。
    /// </summary>
    public static class Convert
    {
        /// <summary>
        /// 将输入数据转换为字节数组。
        /// <para>支持以下类型的输入数据：</para>
        /// <list type="bullet">
        /// <item><see cref="string"/></item>
        /// <item><see cref="float"/></item>
        /// <item><see cref="double"/></item>
        /// <item><see cref="ushort"/></item>
        /// <item><see cref="uint"/></item>
        /// <item><see cref="DateTime"/></item>
        /// <item><see cref="BitArray"/></item>
        /// </list>
        /// </summary>
        /// <typeparam name="T">输入数据的类型。</typeparam>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组。</returns>
        /// <exception cref="ArgumentException">不支持转换输入数据的类型。</exception>
        public static byte[] ToByte<T>(T value)
        {
            if (value == null)
                return null;

            switch (value)
            {
                case byte[] ba: return ba;
                case string s: return ToByte(s);
                case float f: return ToByte(f);
                case double d: return ToByte(d);
                case ushort us: return ToByte(us);
                case uint ui: return ToByte(ui);
                case DateTime da: return ToByte(da);
                case BitArray b: return ToByte(b);
                default: throw new ArgumentException($"不支持从 {typeof(T)} 转换。");
            }
        }

        /// <summary>
        /// 将字节数组转换为 <typeparamref name="T"/> 类型的数据。
        /// <para>支持以下类型的输出数据：</para>
        /// <list type="bullet">
        /// <item><see cref="string"/></item>
        /// <item><see cref="float"/></item>
        /// <item><see cref="double"/></item>
        /// <item><see cref="short"/></item>
        /// <item><see cref="ushort"/></item>
        /// <item><see cref="int"/></item>
        /// <item><see cref="uint"/></item>
        /// <item><see cref="DateTime"/></item>
        /// <item><see cref="BitArray"/></item>
        /// </list>
        /// </summary>
        /// <typeparam name="T">输出数据的类型。</typeparam>
        /// <param name="value">原始数据的字节数组。</param>
        /// <returns><typeparamref name="T"/> 类型的数据。</returns>
        /// <exception cref="ArgumentException">不支持转换为指定的数据类型。</exception>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> 为 <see langword="null"/>。</exception>
        public static T FromByte<T>(byte[] value)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));

            switch (typeof(T).Name)
            {
                case "String": return (T)(object)FromByteToString(value);
                case "Single": return (T)(object)FromByteToFloat(value);
                case "Double": return (T)(object)FromByteToDouble(value);
                case "UInt16": return (T)(object)FromByteToUInt16(value);
                case "Int16": return (T)(object)FromByteToInt16(value);
                case "UInt32": return (T)(object)FromByteToUInt32(value);
                case "Int32": return (T)(object)FromByteToInt32(value);
                case "DateTime": return (T)(object)FromByteToDate(value);
                case "BitArray": return (T)(object)FromByteToBitArray(value);

                default: throw new ArgumentException($"不支持转换为 {typeof(T)}。");
            }
        }

        /// <summary>
        /// 将 <see cref="string"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(string value) => Encoding.ASCII.GetBytes(value);

        /// <summary>
        /// 将字节数组转换为 <see cref="string"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static string FromByteToString(byte[] value) => Encoding.ASCII.GetString(value);

        /// <summary>
        /// 将 <see cref="float"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(float value) => BitConverter.GetBytes(value);

        /// <summary>
        /// 将字节数组转换为 <see cref="float"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static float FromByteToFloat(byte[] value) => BitConverter.ToSingle(value, 0);

        /// <summary>
        /// 将 <see cref="double"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(double value) => BitConverter.GetBytes(value);

        /// <summary>
        /// 将字节数组转换为 <see cref="double"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static double FromByteToDouble(byte[] value) => BitConverter.ToDouble(value, 0);

        /// <summary>
        /// 将 <see cref="ushort"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(ushort value) => BitConverter.GetBytes(value);

        /// <summary>
        /// 将字节数组转换为 <see cref="ushort"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static ushort FromByteToUInt16(byte[] value) => BitConverter.ToUInt16(value, 0);

        /// <summary>
        /// 将字节数组转换为 <see cref="short"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static short FromByteToInt16(byte[] value) => System.Convert.ToInt16(FromByteToUInt16(value));

        /// <summary>
        /// 将 <see cref="uint"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(uint value)
        {
            if (value > 16777215)
                throw new ArgumentOutOfRangeException(nameof(value), "最大允许值为16777215");

            var b = BitConverter.GetBytes(value);
            var result = new byte[3];

            for (var i = 0; i < 3; i++)
                result[i] = b[i];

            return result;
        }

        /// <summary>
        /// 将字节数组转换为 <see cref="uint"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static uint FromByteToUInt32(byte[] value)
        {
            var nByte = new byte[4];
            value.CopyTo(nByte, 0);

            return BitConverter.ToUInt32(nByte, 0);
        }

        /// <summary>
        /// 将字节数组转换为 <see cref="int"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static int FromByteToInt32(byte[] value) => System.Convert.ToInt32(FromByteToUInt32(value));

        /// <summary>
        /// 将 <see cref="DateTime"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(DateTime value)
        {
            var bDay = BitConverter.GetBytes(value.Day);
            var bMonth = BitConverter.GetBytes(value.Month);
            var bYear = BitConverter.GetBytes(value.Year - 1900);

            return new[] { bDay[0], bMonth[0], bYear[0] };
        }

        /// <summary>
        /// 将字节数组转换为 <see cref="DateTime"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static DateTime FromByteToDate(byte[] value)
        {
            var day = BitConverter.ToInt32(new byte[] { value[0], 0, 0, 0 }, 0);
            var month = BitConverter.ToInt32(new byte[] { value[1], 0, 0, 0 }, 0);
            var year = BitConverter.ToInt32(new byte[] { value[2], 0, 0, 0 }, 0);

            return new DateTime(year + 1900, month, day);
        }

        /// <summary>
        /// 将 <see cref="BitArray"/> 转换为字节数组。
        /// </summary>
        /// <param name="value">要转换的数据。</param>
        /// <returns>字节数组</returns>
        private static byte[] ToByte(BitArray value)
        {
            if (value.Count > 8)
                throw new ArgumentOutOfRangeException(nameof(value), "数组值溢出。数组中最多允许8个元素。");

            var result = new byte[1];

            value.CopyTo(result, 0);

            return result;
        }

        /// <summary>
        /// 将字节数组转换为 <see cref="BitArray"/>。
        /// </summary>
        /// <param name="value">要转换的字节数组。</param>
        /// <returns></returns>
        private static BitArray FromByteToBitArray(byte[] value)
        {
            if (value.Length > 1)
                throw new ArgumentOutOfRangeException(nameof(value), "数组值溢出。仅允许1个字节的数据。");

            return new BitArray(value);
        }
    }
}
